/*-------------------------------------------------------------------------
 *
 * expandeddatum.h
 *	  访问“扩展”值表示的声明。
 *
 * 复杂数据类型，特别是容器类型（如数组和记录），通常具有紧凑但不易修改的磁盘表示。
 * 此外，当我们修改它们时，必须重新复制其余值可能非常低效。
 * 因此，我们提供了一个“扩展”表示的概念，该表示仅在内存中使用，优化计算而非存储。
 * 磁盘上出现的格式称为数据类型的“扁平化”表示，因为它必须是连续的字节块。
 * 但是类型可以有一个扩展表示，而不是这样。数据类型必须提供将扩展表示转换回扁平形式的方法。
 *
 * 一个扩展对象旨在跨多个操作生存，但不应极端长寿；例如，它可能是PL/pgSQL过程中的局部变量。
 * 因此，相较于磁盘格式的额外体积是一个值得的权衡。
 *
 * 对扩展对象的引用是一种TOAST指针。
 * 由于Postgres中的长期约定，这意味着该对象的扁平化形式必须始终是变长对象。
 * 幸运的是，这在实践中并不构成限制。
 *
 * 实际上，扩展对象有两种类型的TOAST指针：只读和读写指针。拥有后者之一可授权函数在原地修改值，而不是像通常需要的那样复制。
 * 函数应始终返回它们创建的任何新扩展对象的读写指针。修改参数值的函数必须小心，确保在过程中失败时不会损坏旧值。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/utils/expandeddatum.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef EXPANDEDDATUM_H
#define EXPANDEDDATUM_H

/* 包含指向扩展对象的指针的EXTERNAL数据的大小 */
#define EXPANDED_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_expanded))

/*
 * 必须为任何扩展对象提供的“方法”。
 *
 * get_flat_size：计算展平表示所需的空间（总计，包括头部）。
 *
 * flatten_into：在调用方分配的空间*result中构造展平表示，
 * 大小为allocated_size（这将始终是之前get_flat_size调用的结果；
 * 它被传递以便交叉检查）。
 *
 * 展平表示必须是有效的内联、非压缩的4字节头部varlena对象。
 *
 * 注意：从扩展数据中构造堆元组会调用get_flat_size两次，
 * 因此确保这不会产生过多的开销是值得的。
 */
typedef Size (*EOM_get_flat_size_method) (ExpandedObjectHeader *eohptr);
typedef void (*EOM_flatten_into_method) (ExpandedObjectHeader *eohptr,
										 void *result, Size allocated_size);

/* 扩展对象的方法的函数指针结构 */
typedef struct ExpandedObjectMethods
{
	EOM_get_flat_size_method get_flat_size;
	EOM_flatten_into_method flatten_into;
} ExpandedObjectMethods;

/*
 * 每个扩展对象必须包含此头信息；通常，头信息
 * 嵌入在某个更大的结构中，该结构添加类型特定字段。
 *
 * 假定头对象和所有辅助数据存储在 eoh_context 中，以便
 * 可以通过删除该上下文来释放该对象，或者通过重新父级
 * 上下文来改变其存储生存期。
 * （原则上，该对象可以拥有其他资源，例如 malloc 的
 * 存储，并使用内存上下文重置回调在重置或删除
 * eoh_context 时释放它们。）
 *
 * 我们在标准头中设置了两个 TOAST 指针，一个可读写
 * 和一个只读。这允许函数返回任一种类型的指针
 * 而无需额外分配，特别是不必担心单独 palloc 的对象
 * 是否具有足够的生存期。
 * 但请注意，这些指针只是一个便利；在其他地方出现的
 * 指针对象仍将是合法的。
 *
 * 对于这个的 typedef 声明出现在 postgres.h 中。
 */
struct ExpandedObjectHeader
{
	/* 虚假的 varlena 头信息 */
	int32		vl_len_;		/* 始终为 EOH_HEADER_MAGIC，见下文 */

	/* 指向对象类型所需方法的指针 */
	const ExpandedObjectMethods *eoh_methods;

	/* 包含此头信息和辅助数据的内存上下文 */
	MemoryContext eoh_context;

	/* 此对象的标准 R/W TOAST 指针保存在此处 */
	char		eoh_rw_ptr[EXPANDED_POINTER_SIZE];

	/* 此对象的标准 R/O TOAST 指针保存在此处 */
	char		eoh_ro_ptr[EXPANDED_POINTER_SIZE];
};

/*
 * 对于只读函数，能够处理常规“扁平”varlena 输入或相同
 * 数据类型的扩展输入是很方便的。为了允许确定
 * 参数获取函数返回的哪种情况，ExpandedObjectHeader 的第一个
 * int32 始终包含 -1（EOH_HEADER_MAGIC 到代码）。这有效
 * 是因为没有 4 字节头的 varlena 可以将其作为前四个字节。
 * 注意：我们无法可靠地区分 ExpandedObjectHeader 和一个
 * 短头对象。但如果参数获取代码总是返回一个 4 字节头的
 * 扁平对象或一个扩展对象，这种方法很好用。
 */
#define EOH_HEADER_MAGIC (-1)
#define VARATT_IS_EXPANDED_HEADER(PTR) \
	(((varattrib_4b *) (PTR))->va_4byte.va_header == (uint32) EOH_HEADER_MAGIC)

/*
 * 扩展对象的通用支持函数。
 * （以后可能值得内联更多这些内容。）
 */

#define EOHPGetRWDatum(eohptr)	PointerGetDatum((eohptr)->eoh_rw_ptr)
#define EOHPGetRODatum(eohptr)	PointerGetDatum((eohptr)->eoh_ro_ptr)

/* Datum 是否表示一个可写的扩展对象？ */
#define DatumIsReadWriteExpandedObject(d, isnull, typlen) \
	(((isnull) || (typlen) != -1) ? false : \
	 VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))

#define MakeExpandedObjectReadOnly(d, isnull, typlen) \
	(((isnull) || (typlen) != -1) ? (d) : \
	 MakeExpandedObjectReadOnlyInternal(d))

extern ExpandedObjectHeader *DatumGetEOHP(Datum d);
extern void EOH_init_header(ExpandedObjectHeader *eohptr,
							const ExpandedObjectMethods *methods,
							MemoryContext obj_context);
extern Size EOH_get_flat_size(ExpandedObjectHeader *eohptr);
extern void EOH_flatten_into(ExpandedObjectHeader *eohptr,
							 void *result, Size allocated_size);
extern Datum MakeExpandedObjectReadOnlyInternal(Datum d);
extern Datum TransferExpandedObject(Datum d, MemoryContext new_parent);
extern void DeleteExpandedObject(Datum d);

#endif							/* EXPANDEDDATUM_H */
